{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# QuestDB\n",
    "This tutorial will show you how to get a QuestDB instance up and running locally to test JupySQL. You can run this in a Jupyter notebook."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Pre-requisites\n",
    "\n",
    "To run this tutorial, you need to install following Python packages:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Note: you may need to restart the kernel to use updated packages.\n"
     ]
    }
   ],
   "source": [
    "%pip install jupysql pandas pyarrow --quiet"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You also need a PostgreSQL connector. We recommend using `psycopg2`. The easiest way to install it is via:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Note: you may need to restart the kernel to use updated packages.\n"
     ]
    }
   ],
   "source": [
    "%pip install psycopg2-binary --quiet"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You also need Docker installed and running to start the QuestDB instance."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Start QuestDB instance\n",
    "\n",
    "We fetch the official image, create a new database, and user (this will take 1-2 minutes):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0322c413699420adb1ccb136bc602d0a6514276df34778c90e60cf423ab8aac6\n"
     ]
    }
   ],
   "source": [
    "%%bash\n",
    "docker run --detach --name questdb_ \\\n",
    "    -p 9000:9000 -p 9009:9009 -p 8812:8812 -p 9003:9003 questdb/questdb:7.1"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Our database is running, let's load some data!"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Load sample data\n",
    "\n",
    "Now, let's fetch some sample data. We'll be using the [Penguins dataset](https://raw.githubusercontent.com/mwaskom/seaborn-data/master/penguins.csv):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('penguins.csv', <http.client.HTTPMessage at 0x2dda0ff3810>)"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import urllib.request\n",
    "\n",
    "urllib.request.urlretrieve(\n",
    "    \"https://raw.githubusercontent.com/mwaskom/seaborn-data/master/penguins.csv\",\n",
    "    \"penguins.csv\",\n",
    ")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's create a new table `penguins` in our QuestDB instance and load this csv file into it (this will take about a minute)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [],
   "source": [
    "import requests\n",
    "\n",
    "with open(\"penguins.csv\", \"rb\") as csv:\n",
    "    file_data = csv.read()\n",
    "    files = {\"data\": (\"penguins\", file_data)}\n",
    "    response = requests.post(\"http://127.0.0.1:9000/imp\", files=files)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Query\n",
    "\n",
    "Now, let's start JupySQL, authenticate and start querying the data!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The sql extension is already loaded. To reload it, use:\n",
      "  %reload_ext sql\n"
     ]
    }
   ],
   "source": [
    "%load_ext sql"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Create a new connection using `psycopg2`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [],
   "source": [
    "import psycopg2 as pg\n",
    "\n",
    "engine = pg.connect(\n",
    "    \"dbname='qdb' user='admin' host='127.0.0.1' port='8812' password='quest'\"\n",
    ")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Initialize the connection"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [],
   "source": [
    "%sql engine"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```{important}\n",
    "If the cell above fails, you might have some missing packages. Message us on [Slack](https://ploomber.io/community) and we'll help you!\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```{note} \n",
    "QuestDB now supports a connection string via [sqlalchemy](https://github.com/questdb/questdb/pull/3080#issuecomment-1478334048):\n",
    "\n",
    "`%sql postgresql+psycopg2://admin:quest@localhost:8812/qdb` \n",
    "```"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's run our first queries to count and fetch some data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "*  \"<connection object at 0x000002DD9FE81CF0; dsn: 'user=admin password=xxx dbname=qdb host=127.0.0.1 port=8812', closed: 0>\"\n",
      "1 rows affected.\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<table>\n",
       "    <thead>\n",
       "        <tr>\n",
       "            <th>count</th>\n",
       "        </tr>\n",
       "    </thead>\n",
       "    <tbody>\n",
       "        <tr>\n",
       "            <td>344</td>\n",
       "        </tr>\n",
       "    </tbody>\n",
       "</table>"
      ],
      "text/plain": [
       "[(344,)]"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%%sql\n",
    "SELECT COUNT(*) FROM penguins"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "*  \"<connection object at 0x000002DD9FE81CF0; dsn: 'user=admin password=xxx dbname=qdb host=127.0.0.1 port=8812', closed: 0>\"\n",
      "5 rows affected.\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<table>\n",
       "    <thead>\n",
       "        <tr>\n",
       "            <th>species</th>\n",
       "            <th>island</th>\n",
       "            <th>bill_length_mm</th>\n",
       "            <th>bill_depth_mm</th>\n",
       "            <th>flipper_length_mm</th>\n",
       "            <th>body_mass_g</th>\n",
       "            <th>sex</th>\n",
       "        </tr>\n",
       "    </thead>\n",
       "    <tbody>\n",
       "        <tr>\n",
       "            <td>Adelie</td>\n",
       "            <td>Torgersen</td>\n",
       "            <td>39.1</td>\n",
       "            <td>18.7</td>\n",
       "            <td>181</td>\n",
       "            <td>3750</td>\n",
       "            <td>MALE</td>\n",
       "        </tr>\n",
       "        <tr>\n",
       "            <td>Adelie</td>\n",
       "            <td>Torgersen</td>\n",
       "            <td>39.5</td>\n",
       "            <td>17.4</td>\n",
       "            <td>186</td>\n",
       "            <td>3800</td>\n",
       "            <td>FEMALE</td>\n",
       "        </tr>\n",
       "        <tr>\n",
       "            <td>Adelie</td>\n",
       "            <td>Torgersen</td>\n",
       "            <td>40.3</td>\n",
       "            <td>18.0</td>\n",
       "            <td>195</td>\n",
       "            <td>3250</td>\n",
       "            <td>FEMALE</td>\n",
       "        </tr>\n",
       "        <tr>\n",
       "            <td>Adelie</td>\n",
       "            <td>Torgersen</td>\n",
       "            <td>None</td>\n",
       "            <td>None</td>\n",
       "            <td>None</td>\n",
       "            <td>None</td>\n",
       "            <td>None</td>\n",
       "        </tr>\n",
       "        <tr>\n",
       "            <td>Adelie</td>\n",
       "            <td>Torgersen</td>\n",
       "            <td>36.7</td>\n",
       "            <td>19.3</td>\n",
       "            <td>193</td>\n",
       "            <td>3450</td>\n",
       "            <td>FEMALE</td>\n",
       "        </tr>\n",
       "    </tbody>\n",
       "</table>"
      ],
      "text/plain": [
       "[('Adelie', 'Torgersen', 39.1, 18.7, 181, 3750, 'MALE'),\n",
       " ('Adelie', 'Torgersen', 39.5, 17.4, 186, 3800, 'FEMALE'),\n",
       " ('Adelie', 'Torgersen', 40.3, 18.0, 195, 3250, 'FEMALE'),\n",
       " ('Adelie', 'Torgersen', None, None, None, None, None),\n",
       " ('Adelie', 'Torgersen', 36.7, 19.3, 193, 3450, 'FEMALE')]"
      ]
     },
     "execution_count": 39,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%sql select * from penguins limit 5"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Plotting\n",
    "\n",
    "To utilize JupySQL ggplot API, it is crucial to have valid data, so let's remove null values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "*  \"<connection object at 0x000002DD9FE81CF0; dsn: 'user=admin password=xxx dbname=qdb host=127.0.0.1 port=8812', closed: 0>\"\n",
      "Skipping execution...\n"
     ]
    }
   ],
   "source": [
    "%%sql --save no_nulls --no-execute\n",
    "SELECT *\n",
    "FROM penguins\n",
    "WHERE body_mass_g IS NOT NULL and\n",
    "sex IS NOT NULL"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<sql.ggplot.ggplot.ggplot at 0x2dda0364910>"
      ]
     },
     "execution_count": 41,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkAAAAGzCAYAAADHdKgcAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAABDEUlEQVR4nO3df3yPdf////vLzIZsyOwHY8YM5UcRjZRlzOrsRCVJ+RmR+VmdrJJRZ+vMGYpFdcZ0StT5Dp2FWmJ9iim0nFTzazNiRLbZsGk7vn/03ate9sM2e+312o7b9XI5LnY8j+fzOB7HsVe5O369LIZhGAIAADCRWo4uAAAAoKoRgAAAgOkQgAAAgOkQgAAAgOkQgAAAgOkQgAAAgOkQgAAAgOkQgAAAgOkQgAAAgOkQgAAnEBAQoFGjRjm6jGrpt99+09/+9jf5+/urVq1aGjRokKNLMqVt27bJYrFo27Zt1rZRo0YpICDAYTUBpSEAAZUsLi5OFotFu3btKnZ5nz59dOONN17zdjZu3Kjo6OhrXk91t3z5cs2fP1/333+/Vq5cqenTpzu6pFIFBATweytFYZBKTU11dCmo4Wo7ugAAUnJysmrVKt+/RzZu3KjY2FjT/2X6xRdfqFmzZlq4cKGjSwFQjXAGCHACbm5ucnV1dXQZ5ZKTk+PoEiRJp0+fVsOGDa/a77ffflNeXp79CwJQLRCAACdw5T1Aly9f1ty5cxUUFCR3d3ddf/31uu222xQfHy/p93srYmNjJUkWi8U6FcrJydETTzwhf39/ubm5KTg4WP/85z9lGIbNdi9evKgpU6aoSZMmatCggf7617/q559/lsVisTmzFB0dLYvFoh9++EEPPfSQGjVqpNtuu02StHfvXo0aNUqBgYFyd3eXj4+PxowZo7Nnz9psq3AdBw4c0MMPPyxPT095eXlp9uzZMgxDx44d08CBA+Xh4SEfHx+98sorpR6z1NRUWSwWbd26Vfv377ceg23btlmX/fOf/9SiRYvUunVrubm56YcffpD0+1mj3r17q379+mrYsKEGDhyoH3/80a71lqTwkunXX3+tGTNmyMvLS/Xr19fgwYP1yy+/FOn/+uuv64YbbpCbm5v8/Pw0adIkZWRk2G2bV34WClX0vrU1a9aoa9euatCggTw8PNSxY0e9+uqr5V4PcK24BAbYSWZmps6cOVOk/fLly1cdGx0drZiYGD366KPq3r27srKytGvXLu3Zs0f9+vXTY489phMnTig+Pl7//ve/bcYahqG//vWv2rp1q8aOHasuXbro008/1VNPPaWff/7Z5lLRqFGj9P777+uRRx7RrbfeqoSEBN19990l1jVkyBAFBQXpxRdftIap+Ph4HTlyRKNHj5aPj4/279+vN998U/v371diYqJNMJOkoUOHqn379nrppZf0ySef6IUXXlDjxo31xhtv6M4779Q//vEPvfvuu3ryySd1yy236Pbbby+2Fi8vL/373//W3//+d2VnZysmJkaS1L59e128eFGStGLFCl26dEnjx4+Xm5ubGjdurM8//1wREREKDAxUdHS0Ll68qMWLF6tXr17as2dPkZt2K6veq5k8ebIaNWqkOXPmKDU1VYsWLVJkZKTWrl1r7RMdHa25c+cqLCxMEydOVHJyspYuXapvv/1WX3/9dbnPIpZlm5UpPj5ew4YNU9++ffWPf/xDkvTjjz/q66+/1tSpU+2yTaBEBoBKtWLFCkNSqdMNN9xgM6Zly5bGyJEjrfOdO3c27r777lK3M2nSJKO4/4TXr19vSDJeeOEFm/b777/fsFgsxqFDhwzDMIzdu3cbkoxp06bZ9Bs1apQhyZgzZ461bc6cOYYkY9iwYUW2d+HChSJt7733niHJ+PLLL4usY/z48da23377zWjevLlhsViMl156ydp+7tw5o27dujbHpCR33HFHkeOZkpJiSDI8PDyM06dP2yzr0qWL0bRpU+Ps2bPWtu+//96oVauWMWLECLvXe6XCz0tYWJhRUFBgbZ8+fbrh4uJiZGRkGIZhGKdPnzbq1Klj9O/f38jPz7f2W7JkiSHJWL58eaVv0zCMIp+FQld+Zrdu3WpIMrZu3WptGzlypNGyZUvr/NSpUw0PDw/jt99+K3OtgL1wCQywk9jYWMXHxxeZOnXqdNWxDRs21P79+3Xw4MFyb3fjxo1ycXHRlClTbNqfeOIJGYahTZs2SZI2b94sSXr88cdt+k2ePLnEdU+YMKFIW926da0/X7p0SWfOnNGtt94qSdqzZ0+R/o8++qj1ZxcXF3Xr1k2GYWjs2LHW9oYNGyo4OFhHjhwpsZayuO++++Tl5WWdP3nypJKSkjRq1Cg1btzY2t6pUyf169dPGzdudFi948ePtzlb1rt3b+Xn5+vo0aOSpM8//1x5eXmaNm2azQ3z48aNk4eHhz755JNK32Zla9iwoXJycqyXcgFHIgABdtK9e3eFhYUVmRo1anTVsfPmzVNGRobatm2rjh076qmnntLevXvLtN2jR4/Kz89PDRo0sGlv3769dXnhn7Vq1VKrVq1s+rVp06bEdV/ZV5J+/fVXTZ06Vd7e3qpbt668vLys/TIzM4v0b9Gihc28p6en3N3d1aRJkyLt586dK7GWsriy3sJ9Dw4OLtK3ffv2OnPmTJGbu6uq3iu3U/g5KVxnSbXXqVNHgYGBFQotV9tmZXv88cfVtm1bRUREqHnz5hozZow1iANVjQAEOKHbb79dhw8f1vLly3XjjTfqX//6l26++Wb961//cmhdfz7bU+iBBx7QW2+9pQkTJujDDz/UZ599Zv1LraCgoEh/FxeXMrVJKnLTdmXUW15VVa+9joG9tpmfn1/u7TVt2lRJSUn66KOPrPepRUREaOTIkeVeF3CtCECAk2rcuLFGjx6t9957T8eOHVOnTp1snsa58ubiQi1bttSJEyd0/vx5m/affvrJurzwz4KCAqWkpNj0O3ToUJlrPHfunLZs2aJZs2Zp7ty5Gjx4sPr166fAwMAyr6MqFe57cnJykWU//fSTmjRpovr161d1WWVSUu15eXlKSUmxLq9sjRo1KvKUWV5enk6ePFmh9dWpU0f33HOPXn/9dR0+fFiPPfaY3nnnnXJ97oDKQAACnNCVj5Bfd911atOmjXJzc61thX9RX/mX01133aX8/HwtWbLEpn3hwoWyWCyKiIiQJIWHh0v6/bHqP1u8eHGZ6yw8g3DlGYNFixaVeR1VydfXV126dNHKlSttjtu+ffv02Wef6a677nJccVcRFhamOnXq6LXXXrM53m+//bYyMzNLfXrvWrRu3VpffvmlTdubb75ZoTNAV36ua9WqZb0n7s+fbaAq8Bg84IQ6dOigPn36qGvXrmrcuLF27dql//znP4qMjLT26dq1qyRpypQpCg8Pl4uLix588EHdc889Cg0N1TPPPKPU1FR17txZn332mTZs2KBp06apdevW1vH33XefFi1apLNnz1ofgz9w4ICkks8w/ZmHh4duv/12vfzyy7p8+bKaNWumzz77rMhZJWcyf/58RUREKCQkRGPHjrU+Bu/p6enUb9X28vJSVFSU5s6dqwEDBuivf/2rkpOT9frrr+uWW27Rww8/bJftPvroo5owYYLuu+8+9evXT99//70+/fTTIvdAlXVdv/76q+688041b95cR48e1eLFi9WlSxfrPWpAVSEAAU5oypQp+uijj/TZZ58pNzdXLVu21AsvvKCnnnrK2ufee+/V5MmTtWbNGq1atUqGYejBBx9UrVq19NFHH+m5557T2rVrtWLFCgUEBGj+/Pl64oknbLbzzjvvyMfHR++9957WrVunsLAwrV27VsHBwXJ3dy9TratXr9bkyZMVGxsrwzDUv39/bdq0SX5+fpV6TCpLWFiYNm/erDlz5ui5556Tq6ur7rjjDv3jH/8o9iZvZxIdHS0vLy8tWbJE06dPV+PGjTV+/Hi9+OKLdnuT+Lhx45SSkqK3335bmzdvVu/evRUfH6++ffuWe10PP/yw3nzzTb3++uvKyMiQj4+Phg4dqujo6HJ/FQxwrSyGPe+wA1DtJCUl6aabbtKqVas0fPhwR5cDAHZB5AZMrPCNyX+2aNEi1apVq8JvNAaA6oBLYICJvfzyy9q9e7dCQ0NVu3Ztbdq0SZs2bdL48ePl7+/v6PJQThcvXiz23Ut/1rhxY9WpU6eKKgKcF5fAABOLj4/X3Llz9cMPPyg7O1stWrTQI488omeeeUa1a/Pvo+omLi5Oo0ePLrXP1q1b1adPn6opCHBiBCAAqCFOnjyp/fv3l9qna9euZXobOVDTEYAAAIDpcBM0AAAwHS7yF6OgoEAnTpxQgwYNyvQyOAAA4HiGYej8+fPy8/O76rulCEDFOHHiBE/AAABQTR07dkzNmzcvtQ8BqBgNGjSQ9PsB9PDwcHA1AACgLLKysuTv72/9e7w0BKBiFF728vDwIAABAFDNlOX2FW6CBgAApkMAAgAApkMAAgAApsM9QEA1ZxiGfvvtN+Xn5zu6FNRQLi4uql27Nq8FQY1CAAKqsby8PJ08eVIXLlxwdCmo4erVqydfX1++SBU1BgEIqKYKCgqUkpIiFxcX+fn5qU6dOvwLHZXOMAzl5eXpl19+UUpKioKCgq76gjmgOiAAAdVUXl6eCgoK5O/vr3r16jm6HNRgdevWlaurq44ePaq8vDy5u7s7uiTgmhHjgWqOf42jKvA5Q03DJxoAAJgOAQgAAJiOQ+8BiomJ0YcffqiffvpJdevWVc+ePfWPf/xDwcHB1j6XLl3SE088oTVr1ig3N1fh4eF6/fXX5e3tXeJ6DcPQnDlz9NZbbykjI0O9evXS0qVLFRQUVBW7BTjcwvgDVbat6f3alntMnz591KVLFy1atKjY5QEBAZo2bZqmTZsm6ffX2q9bt06DBg1SamqqWrVqpe+++05dunQpdTvbtm1TaGiozp07p4YNG5a7zsp2tf0GUHUcegYoISFBkyZNUmJiouLj43X58mX1799fOTk51j7Tp0/Xf//7X33wwQdKSEjQiRMndO+995a63pdfflmvvfaali1bpp07d6p+/foKDw/XpUuX7L1LACrBt99+q/Hjxzu6jArbtm2bLBaLMjIyHF0KgBI49AzQ5s2bbebj4uLUtGlT7d69W7fffrsyMzP19ttva/Xq1brzzjslSStWrFD79u2VmJioW2+9tcg6DcPQokWL9Oyzz2rgwIGSpHfeeUfe3t5av369HnzwQfvvGIBr4uXl5egSANRwTnUPUGZmpiSpcePGkqTdu3fr8uXLCgsLs/Zp166dWrRooR07dhS7jpSUFKWnp9uM8fT0VI8ePUock5ubq6ysLJsJgH399ttvioyMlKenp5o0aaLZs2fLMAxJv18Cs9dloq+++kq9e/dW3bp15e/vrylTpticdQ4ICNCLL76oMWPGqEGDBmrRooXefPNNm3Vs375dXbp0kbu7u7p166b169fLYrEoKSlJqampCg0NlSQ1atRIFotFo0aNso4tKCjQ3/72NzVu3Fg+Pj6Kjo4uc+0Wi0VvvPGG/vKXv6hevXpq3769duzYoUOHDqlPnz6qX7++evbsqcOHD1vHREdHq0uXLlq+fLlatGih6667To8//rjy8/P18ssvy8fHR02bNtXf//73ih1QoJpymvcAFRQUaNq0aerVq5duvPFGSVJ6errq1KlT5Nq9t7e30tPTi11PYfuV9wiVNiYmJkZz5869xj0wsa0xv/8ZGuXYOlCtrFy5UmPHjtU333yjXbt2afz48WrRooXGjRtnt20ePnxYAwYM0AsvvKDly5frl19+UWRkpCIjI7VixQprv1deeUXPP/+8nn76af3nP//RxIkTdccddyg4OFhZWVm65557dNddd2n16tU6evSo9V4lSfL399f//d//6b777lNycrI8PDxUt25dm/2eMWOGdu7cqR07dmjUqFHq1auX+vXrV6Z9eP7557VgwQItWLBA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+uabb/Tqq69qwYIF+te//lWp2zh27Jjuvfde3XPPPUpKStKjjz6qWbNm2fQ5fPiwBgwYoPvuu0979+7V2rVr9dVXXykyMtKm3/z589W5c2d99913mjVrlqZOnar4+HhJv1+yk6QVK1bo5MmT1vnC9a9fv14ff/yxPv74YyUkJOill14q8z6sXLlSTZo00TfffKPJkydr4sSJGjJkiHr27Kk9e/aof//+euSRR4qEmejoaC1ZskTbt2/XsWPH9MADD2jRokVavXq1PvnkE3322WdavHhxuY4nUJMQgAA4hL+/vxYuXKjg4GANHz5ckydP1sKFCyt1G0uXLlXr1q31yiuvWLczatQomz4xMTEaPny4pk2bpqCgIPXs2VOvvfaa3nnnHV269Md3YfXq1UuzZs1S27ZtNXnyZN1///3Wegsv2TVs2FA+Pj42l/AKCgoUFxenG2+8Ub1799YjjzyiLVu2lHkfOnfurGeffVZBQUGKioqSu7u7mjRponHjxikoKEjPPfeczp49q71799qMe+GFF9SrVy/ddNNNGjt2rBISErR06VLddNNN6t27t+6//35t3bq1vIcUqDEIQAAc4tZbb5XFYrHOh4SE6ODBg8rPz6+0bfz444/q0aOHTVtISIjN/Pfff6+4uDibb18PDw9XQUGBUlJSShwXEhKiH3/88ao1BAQE2HzHl6+vr06fPl3mfejUqZP1ZxcXF11//fXq2LGjtc3b21uSiqzzz+O8vb1Vr149BQYG2rSVpw6gpuEmaACmlp2drccee0xTpkwpsqxFixbXvH5XV1ebeYvFooKCgmsa/+e2whB55Tqv7HOtdQA1DQEIgEPs3LnTZj4xMVFBQUFycXGptG20b99eH330UZHt/NnNN9+sH374QW3atCl1XVeOS0xMVPv27a3zrq6ulXr2CoB9cQkMgEOkpaVpxowZSk5O1nvvvafFixdr6tSplbqNCRMm6ODBg3rqqaeUnJys1atXKy4uzqbPzJkztX37dkVGRiopKUkHDx7Uhg0bitwE/fXXX+vll1/WgQMHFBsbqw8++MCm3oCAAG3ZskXp6ek6d+5cpe4HgMrHGSCgJqoGL7McMWKELl68qO7du8vFxUVTp07V+PHjK3UbLVq00P/93/9p+vTpWrx4sbp3764XX3xRY8aMsfbp1KmTEhIS9Mwzz6h3794yDEOtW7fW0KFDbdb1xBNPaNeuXZo7d648PDy0YMEChYeHW5e/8sormjFjht566y01a9as2MftATgPi1H44g1YZWVlydPTU5mZmfLw8HB0OVWnom+C5g3SDnHp0iWlpKSoVatWcnd3d3Q5NVpAQICmTZtW6td31ESnsv54Cu5yXq5+Tjuq78656mLB1S9TTu/X1p6lAcUqz9/fXAIDAACmQwACUG1NmDDB5vH1P08TJkxwdHmlSktLK7H26667TmlpaY4uEajRuAcIQLU1b948Pfnkk8Uuq8zL1/a4n8fPz6/Ub7T38/Or9G0C+AMBCEC11bRpUzVt2tTRZVRI7dq1r/roPQD74RIYUM3xHAOqhPVzZim1G1BdEICAaqrwzb58ozeqQl7uJeUbhvIKCECoGbgEBlRTLi4uatiwofX7nOrVq2fz3VrAtbqclysZhvJyL+nsmV904kIt5RdzBijxyNkibQvjD5R5OzwyD0cgAAHVmI+Pj6SiX4QJVIasi5clSfmGoRMXaunYpToOrgioPAQgoBqzWCzy9fVV06ZNdfnyZUeXgxom7usUSRblFViKPfMDVGcEIKAGcHFxqdQvEQUklemNz0B1xU3QAADAdBwagL788kvdc8898vPzk8Vi0fr1622WWyyWYqf58+eXuM7o6Ogi/du1a2fnPQEAANWJQwNQTk6OOnfurNjY2GKXnzx50mZavny5LBaL7rvvvlLXe8MNN9iM++qrr+xRPgAAqKYceg9QRESEIiIiSlxe+IRLoQ0bNig0NFSBgYGlrrd27dpFxgIAABSqNvcAnTp1Sp988onGjh171b4HDx6Un5+fAgMDNXz48Kt+qWBubq6ysrJsJgAAUHNVmwC0cuVKNWjQQPfee2+p/Xr06KG4uDht3rxZS5cuVUpKinr37q3z58+XOCYmJkaenp7Wyd/fv7LLBwAATqTaBKDly5dr+PDhcnd3L7VfRESEhgwZok6dOik8PFwbN25URkaG3n///RLHREVFKTMz0zodO3assssHAABOpFq8B+j//b//p+TkZK1du7bcYxs2bKi2bdvq0KFDJfZxc3OTm5vbtZQIAACqkWpxBujtt99W165d1blz53KPzc7O1uHDh+Xr62uHygAAQHXk0ACUnZ2tpKQkJSUlSZJSUlKUlJRkc9NyVlaWPvjgAz366KPFrqNv375asmSJdf7J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AAA4N6c+A3Sl7OxsHT58WL6+vsUuDwkJ0ZYtW2za4uPjFRISUhXlAQCAasKpA9CTTz6phIQEpaamavv27Ro8eLBcXFw0bNgwSdKIESMUFfXHvSRTp07V5s2b9corr+inn35SdHS0du3apcjISEftAgAAcEJOfQns+PHjGjZsmM6ePSsvLy/ddtttSkxMlJeXlyQpLS1NtWr9keF69uyp1atX69lnn9XTTz+toKAgrV+/XjfeeKOjdgEAADghpw5Aa9asKXX5tm3birQNGTJEQ4YMsVNFAACgJnDqS2AAAAD2QAACAACmQwACAACmQwACAACmQwACAACmQwACAACmQwACAACmQwACAACmQwACAACmQwACAACmQwACAACm49TfBYZSbI0p/ueaonCfQqMcWwfsZmH8gTL1Szxy1s6VADAjzgABAADTIQABAADTIQABAADTIQABAADTIQABAADTIQABAADTIQABAADTIQABAADTIQABAADTIQABAADTIQABAADTIQABAADTIQABAADTIQABAADTIQABAADTIQABAADTqe3oAiBpa0zN2k5Z/LmWwp9Do0rvW9Jy1BiJR84WaTt+7oIDKjEXjnHZFHecEo8U7bcw/kCRtun92tqjJFwDzgABAADTIQABAADTIQABAADTIQABAADTceoAFBMTo1tuuUUNGjRQ06ZNNWjQICUnJ5c6Ji4uThaLxWZyd3evoooBAEB14NQBKCEhQZMmTVJiYqLi4+N1+fJl9e/fXzk5OaWO8/Dw0MmTJ63T0aNHq6hiAABQHTj1Y/CbN2+2mY+Li1PTpk21e/du3X777SWOs1gs8vHxsXd5AACgmnLqM0BXyszMlCQ1bty41H7Z2dlq2bKl/P39NXDgQO3fv7/U/rm5ucrKyrKZAABAzVVtAlBBQYGmTZumXr166cYbbyyxX3BwsJYvX64NGzZo1apVKigoUM+ePXX8+PESx8TExMjT09M6+fv722MXAACAk6g2AWjSpEnat2+f1qxZU2q/kJAQjRgxQl26dNEdd9yhDz/8UF5eXnrjjTdKHBMVFaXMzEzrdOzYscouHwAAOBGnvgeoUGRkpD7++GN9+eWXat68ebnGurq66qabbtKhQ4dK7OPm5iY3N7drLRMAAFQTTn0GyDAMRUZGat26dfriiy/UqlWrcq8jPz9f//vf/+Tr62uH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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from sql.ggplot import ggplot, aes, geom_histogram\n",
    "\n",
    "(\n",
    "    ggplot(\n",
    "        table=\"no_nulls\",\n",
    "        with_=\"no_nulls\",\n",
    "        mapping=aes(x=[\"bill_length_mm\", \"bill_depth_mm\"]),\n",
    "    )\n",
    "    + geom_histogram(bins=50)\n",
    ")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Clean up"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CONTAINER ID   IMAGE                 COMMAND                  CREATED          STATUS         PORTS                                                                                            NAMES\n",
      "0322c4136994   questdb/questdb:7.1   \"/docker-entrypoint.…\"   10 seconds ago   Up 9 seconds   0.0.0.0:8812->8812/tcp, 0.0.0.0:9000->9000/tcp, 0.0.0.0:9003->9003/tcp, 0.0.0.0:9009->9009/tcp   questdb_\n"
     ]
    }
   ],
   "source": [
    "! docker container ls"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture out\n",
    "! docker ps -a -q --filter=\"name=questdb\" --quiet"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Container id: 0322c4136994\n"
     ]
    }
   ],
   "source": [
    "container_id = out.stdout.strip()\n",
    "print(f\"Container id: {container_id}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0322c4136994\n"
     ]
    }
   ],
   "source": [
    "! docker container stop {container_id}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0322c4136994\n"
     ]
    }
   ],
   "source": [
    "! docker container rm {container_id}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES\n"
     ]
    }
   ],
   "source": [
    "! docker container ls"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "jupysql311",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.3"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
